package com.bcbim.model_server.file.service;

import com.alibaba.fastjson.JSONObject;
import com.bcbim.common.sys.Constant;
import com.bcbim.common.util.GuidUtil;
import com.bcbim.model_server.component.mapper.*;
import com.bcbim.model_server.file.mapper.FileMapper;
import com.bcbim.model_server.floor.mapper.FloorMapper;
import com.bcbim.model_server_interface.component.bo.ComponentTreeBO;
import com.bcbim.model_server_interface.component.dao.*;
import com.bcbim.model_server_interface.file.bo.RevitResultInfo;
import com.bcbim.model_server_interface.file.dao.FileDAO;
import com.bcbim.model_server_interface.file.dto.ElementFileDTO;
import com.bcbim.model_server_interface.file.dto.ElementInfoDTO;
import com.bcbim.model_server_interface.file.dto.FileListDTO;
import com.bcbim.model_server_interface.file.service.FileService;
import com.bcbim.model_server_interface.file.vo.FileInfoVO;
import com.bcbim.model_server_interface.file.vo.FileModelInfoVO;
import com.bcbim.model_server_interface.floor.dao.FloorDAO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Description:
 * @Author: ye
 * @Date: 2022/3/24
 * @Version: V1.0
 **/
@Slf4j
@Service(timeout = 60 * 1000, group = "file", version = "1.0.0")
public class FileServiceImpl implements FileService {

    @Autowired
    private FileMapper fileMapper;
    @Autowired
    private ComponentMapper componentMapper;
    @Autowired
    private MaterialMapper materialMapper;
    @Autowired
    private ComponentMaterialMapper componentMaterialMapper;
    @Autowired
    private ComponentTreeMapper componentTreeMapper;
    @Autowired
    private FloorMapper floorMapper;
    @Autowired
    private ComponentParamMapper componentParamMapper;

    /**
     * @Description: 保存文件信息
     * @Param: FileDAO
     * @Return: int
     * @Author: ye
     * @Date: 2022/4/11
     */
    @Override
    public int insertFile(FileDAO fileDO) {
        return fileMapper.insertFile(fileDO);
    }

    /**
     * @Description: 后台解析
     * @Param:
     * @Return:
     * @Author: ye
     * @Date: 2022/4/11
     */
    @Override
    @Async("asyncServiceExecutor")
    public void parsingFile(String revitFilePath, String fileName, FileDAO fileDAO, String appKey, long fileSize) {
        String substring = revitFilePath.substring(0, revitFilePath.lastIndexOf("\\") + 1);
        log.info("开始解析");
        try {
            String versionPath = getVersionPath(revitFilePath);
            log.info("versionPath = " + versionPath);
            if (StringUtils.isEmpty(versionPath)) {
                log.info("版本不对");
                fileDAO.setFileStatusType("3");
                fileMapper.updateFileById(fileDAO);
                return;
            }
            Process process = new ProcessBuilder(versionPath, revitFilePath.replace("\\", "/")).start();
            process.waitFor();
            String resultPath = substring + "result.txt";
            File file = new File(resultPath);
            if (!file.isFile()) {
                log.info("解析错误");
                fileDAO.setFileStatusType("3");
                fileMapper.updateFileById(fileDAO);
                return;
            }
            Path infoPath = Paths.get(resultPath);
            String decompress = new String(Files.readAllBytes(infoPath), "UTF-8");

            RevitResultInfo revitResultInfo = JSONObject.parseObject(decompress, RevitResultInfo.class);
            if (ObjectUtils.isEmpty(revitResultInfo)) {
                fileDAO.setFileStatusType("3");
                fileMapper.updateFileById(fileDAO);
                return;
            }
            if (revitResultInfo.getCode() == 400) {
                fileDAO.setFileStatusType("3");
                fileMapper.updateFileById(fileDAO);
                return;
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
            fileDAO.setFileStatusType("3");
            fileMapper.updateFileById(fileDAO);
            return;
        }

        Path infoPath = Paths.get(substring + "model\\" + fileName + "_info.bcm");
        byte[] data = new byte[0];
        try {
            data = Files.readAllBytes(infoPath);
//            String decompress = LzUtils.decompress(data);
            String decompress =  new String(data, "UTF-8");
            parsingModelInfo(decompress, revitFilePath, fileDAO);
            fileMapper.updateApplicationResidualFlow(appKey, fileSize);
        } catch (Exception e) {
            fileDAO.setFileStatusType("3");
            fileMapper.updateFileById(fileDAO);
            log.info(e.toString());
        }
    }

    /**
     * @Description: 根据id查询文件信息
     * @Param: fileId
     * @Return: FileInfoVO
     * @Author: ye
     * @Date: 2022/4/14
     */
    @Override
    public FileModelInfoVO getFileModelById(String fileId) {
        return fileMapper.getFileModelById(fileId);
    }

    /**
     * @Description: 查询文件信息
     * @Param: List<String>
     * @Return: List<FileInfoVO>
     * @Author: ye
     * @Date: 2022/5/6
     */
    @Override
    public List<FileInfoVO> getFileInfo(String[] fileIds) {
        return fileMapper.getFileInfo(fileIds);
    }

    /**
     * @Description: 查询构件树文件根节点
     * @Param: id
     * @Return: ComponentTreeBO
     * @Author: ye
     * @Date: 2022/5/11
     */
    @Override
    public ComponentTreeBO getFileRoot(String id) {
        return fileMapper.getFileRoot(id);
    }

    /**
     * @Description: 文件列表数量
     * @Param:
     * @Return: int
     * @Author: ye
     * @Date: 2022/3/25
     */
    @Override
    public int countFile(FileListDTO query) {
        return fileMapper.countFile(query);
    }

    /**
     * @Description: 文件列表
     * @Param:
     * @Return: List<FileInfoVO>
     * @Author: ye
     * @Date: 2022/3/25
     */
    @Override
    public List<FileInfoVO> listFile(FileListDTO query) {
        return fileMapper.listFile(query);
    }

    /**
     * @Description: 删除文件
     * @Param: fileId
     * @Return: int
     * @Author: ye
     * @Date: 2022/3/25
     */
    @Override
    public int removeFile(String fileId) {
        return fileMapper.removeFile(fileId);
    }

    /**
     * @Description: 查询文件版本
     * @Param: revitFilePath
     * @Return: String
     * @Author: ye
     * @Date: 2022/4/14
     */
    private String getVersionPath(String revitFilePath) throws IOException {
        for (int i = 0; i < 20; i++) {
            FileInputStream input = new FileInputStream(revitFilePath);
            input.skip(i);
            String matcher = findMatcher(input);
            switch (matcher) {
                // TODO: 2022/7/8 revit版本更新
                case "2018":
                    return Constant.REVIT2018;
                case "2019":
                    return Constant.REVIT2019;
                case "2020":
                    return Constant.REVIT2020;
                case "2021":
                    return Constant.REVIT2021;
                default:
            }
        }
        return "";
    }

    public String findMatcher(FileInputStream input) throws IOException {
        byte[] buffer = new byte[2000];
        while (input.read(buffer) != -1) {
            String bufferString = new String(buffer, StandardCharsets.UTF_16);
            Pattern pattern = Pattern.compile(Constant.REVIT_VERSION);
            Matcher m = pattern.matcher(bufferString);
            if (m.find()) {
                return m.group(0);
            }
        }
        return "";
    }

    /**
     * @Description: 模型信息保存整理
     * @Param: s json文件
     * @Param: fileId 文件id
     * @Return:
     * @Author: ye
     * @Date: 2022/3/28
     */
    private void parsingModelInfo(String s, String revitFilePath, FileDAO file) {
        List<ComponentDAO> arrElement = new ArrayList<>();
        Map<String, MaterialDAO> arrMaterialMap = new HashMap<>();
        List<MComponentMaterialDAO> arrElementMaterial = new ArrayList<>();
        List<ComponentTreeDAO> arrComponentTree = new ArrayList<>();
        Map<String, FloorDAO> floorMap = new HashMap<>();
        List<ComponentParamDAO> arrComponentParam = new ArrayList<>();

        Map<String, ComponentTreeDAO> treeMap = new HashMap<>();

        ComponentDAO componentDAO;

        ElementFileDTO elementInfoDTO = JSONObject.parseObject(s, ElementFileDTO.class);
        for (ElementInfoDTO info : elementInfoDTO.getE()) {
            componentDAO = new ComponentDAO();
            componentDAO.setId(info.getId());
            componentDAO.setFileId(file.getId());
            componentDAO.setRevitId(info.getUnqiueId());
            componentDAO.setComponentName(info.getName() + " " + info.getIndex());
            componentDAO.setX(info.getCenter().get(0).toString());
            componentDAO.setY(info.getCenter().get(1).toString());
            componentDAO.setZ(info.getCenter().get(2).toString());
            componentDAO.setCategory(info.getCategory());
            arrElement.add(componentDAO);

            if (ObjectUtils.isNotEmpty(info.getParam()) && info.getParam().size() > 0) {
                Iterator<Map.Entry<String, String>> paramIterator = info.getParam().entrySet().iterator();

                while (paramIterator.hasNext()) {
                    Map.Entry<String, String> next = paramIterator.next();
                    ComponentParamDAO param = new ComponentParamDAO();
                    param.setId(GuidUtil.get());
                    param.setComponentId(componentDAO.getId());
                    param.setKey(next.getKey());
                    param.setValue(next.getValue());
                    arrComponentParam.add(param);
                }
            }

            Map<String, List<Integer>> mesh = info.getMaterialFaceIndex();
            Iterator<Map.Entry<String, List<Integer>>> iterator = mesh.entrySet().iterator();

            while (iterator.hasNext()) {
                Map.Entry<String, List<Integer>> next = iterator.next();
                if (!arrMaterialMap.containsKey(next.getKey())) {
                    MaterialDAO materialDAO = new MaterialDAO();
                    materialDAO.setId(next.getKey());
                    materialDAO.setFileId(file.getId());
                    arrMaterialMap.put(next.getKey(), materialDAO);
                }
                for (int i = 0; i < next.getValue().size(); i++) {
                    MComponentMaterialDAO mComponentMaterialDAO = new MComponentMaterialDAO();
                    mComponentMaterialDAO.setId(GuidUtil.get());
                    mComponentMaterialDAO.setComponentId(componentDAO.getId());
                    mComponentMaterialDAO.setMaterialId(arrMaterialMap.get(next.getKey()).getId());
                    mComponentMaterialDAO.setStartIndex(next.getValue().get(i++));
                    mComponentMaterialDAO.setEndIndex(next.getValue().get(i));
                    arrElementMaterial.add(mComponentMaterialDAO);
                }
            }

            if (!floorMap.containsKey(info.getLevel())) {
                floorMap.put(info.getLevel(), new FloorDAO(GuidUtil.get(), file.getId(), info.getLevel()));
            }
            componentDAO.setFloorId(floorMap.get(info.getLevel()).getId());

            if (!treeMap.containsKey(info.getLevel())) {
                ComponentTreeDAO treeDAO = new ComponentTreeDAO();
                treeDAO.setId(GuidUtil.get());
                treeDAO.setFileId(file.getId());
                treeDAO.setTitle(info.getLevel());
                treeMap.put(info.getLevel(), treeDAO);
                arrComponentTree.add(treeDAO);
            }
            String second = info.getLevel() + ";" + info.getCategory();
            if (!treeMap.containsKey(second)) {
                ComponentTreeDAO treeDAO = new ComponentTreeDAO();
                treeDAO.setId(GuidUtil.get());
                treeDAO.setFileId(file.getId());
                treeDAO.setTitle(info.getCategory());
                treeDAO.setParentId(treeMap.get(info.getLevel()).getId());
                treeMap.put(second, treeDAO);
                arrComponentTree.add(treeDAO);
            }
            String third = second + ";" + info.getFamilyName();
            if (!treeMap.containsKey(third)) {
                ComponentTreeDAO treeDAO = new ComponentTreeDAO();
                treeDAO.setId(GuidUtil.get());
                treeDAO.setFileId(file.getId());
                treeDAO.setTitle(info.getFamilyName());
                treeDAO.setParentId(treeMap.get(second).getId());
                treeMap.put(third, treeDAO);
                arrComponentTree.add(treeDAO);
            }
            String fourth = third + ":" + info.getFamilyType();
            if (!treeMap.containsKey(fourth)) {
                ComponentTreeDAO treeDAO = new ComponentTreeDAO();
                treeDAO.setId(GuidUtil.get());
                treeDAO.setFileId(file.getId());
                treeDAO.setTitle(info.getFamilyType());
                treeDAO.setParentId(treeMap.get(third).getId());
                treeMap.put(fourth, treeDAO);
                arrComponentTree.add(treeDAO);
            }
            componentDAO.setComponentTreeId(treeMap.get(fourth).getId());
        }
        saveFileInfo(arrElement, arrMaterialMap.values(), arrElementMaterial, arrComponentTree, floorMap.values(), arrComponentParam);
        file.setNumber(new File(revitFilePath.substring(0, revitFilePath.lastIndexOf("\\") + 1) + "model").listFiles().length - 4);
        file.setFileStatusType("2");
        double[] boundaryXYZ = elementInfoDTO.getB();
        StringBuilder boundary = new StringBuilder();
        for (double p : boundaryXYZ) {
            boundary.append(p).append(";");
        }
        file.setBoundary(boundary.toString());
        fileMapper.updateFileById(file);
    }

    /**
     * @Description: 保存构件信息
     * @Param: List<ComponentDAO> 组件信息
     * @Param: Collection<MaterialDAO> 材质信息
     * @Param: List<ComponentDAO> 组件材质面映射
     * @Return:
     * @Author: ye
     * @Date: 2022/3/28
     */
    @Transactional(rollbackFor = Exception.class)
    void saveFileInfo(List<ComponentDAO> arrComponent,
                      Collection<MaterialDAO> arrMaterial,
                      List<MComponentMaterialDAO> arrComponentMaterial,
                      List<ComponentTreeDAO> arrComponentTree,
                      Collection<FloorDAO> arrFloor,
                      List<ComponentParamDAO> arrComponentParam) {
        if (arrComponent != null && arrComponent.size() > 0) {
            double product = (double) arrComponent.size() / 2000;
            int index = (int)Math.ceil(product);
            for (int i=0;i < index;i++) {
                componentMapper.insertComponents(arrComponent.stream().skip(i * 2000).limit(2000).collect(Collectors.toList()));
            }
        }

        materialMapper.insertMaterials(arrMaterial);

        if (arrComponentMaterial != null && arrComponentMaterial.size() > 0) {
            double product = (double) arrComponentMaterial.size() / 2000;
            // 向上取整
            int index = (int)Math.ceil(product);
            for (int i=0;i < index;i++) {
                componentMaterialMapper.insertComponentMaterials(arrComponentMaterial.stream().skip(i * 2000).limit(2000).collect(Collectors.toList()));
            }
        }

        componentTreeMapper.insertComponentTree(arrComponentTree);

        floorMapper.insert(arrFloor);

        if (arrComponentParam != null && arrComponentParam.size() > 0) {
            double product = (double) arrComponentParam.size() / 2000;
            // 向上取整
            int index = (int)Math.ceil(product);
            for (int i=0;i < index;i++) {
                componentParamMapper.insertComponentParam(arrComponentParam.stream().skip(i * 2000).limit(2000).collect(Collectors.toList()));
            }
        }
        System.out.println("success");
    }


}
